package com.boarsoft.boar.sequence.client;

import java.util.Date;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.annotation.PostConstruct;

import com.boarsoft.boar.sequence.bean.SeqNumber;

/**
 * <p>
 * 方案二：允许分段发号，不丢号，可分区<br>
 * 先从回收号码队列取，取不到再采用方案一。<br>
 * 退号时先写回收号码表，再入队。<br>
 * 启动时从回收号码表装载号码入队。<br>
 * </p>
 * 
 * @author Mac_J
 *
 */
public class EachSegSeqImpl extends BasicSegSeqImpl {
	/** 序号过期时间，单位分钟 */
	protected int period = 30;

	/** 回收队列 */
	protected Queue<SeqNumber> queue = new ConcurrentLinkedQueue<>();

	@PostConstruct
	public void init() {
		super.init();
		// 从数据库装载回收号
		seqNoBiz.reload(catalog, code, this.tag());
	}

	@Override
	public synchronized long next() {
		// 从本地号码表查询可用的最小号码并用乐观锁更新其状态
		SeqNumber seqNo = queue.poll();
		long now = new Date().getTime();
		while (seqNo != null) {
			// 应用从本地内存中的序列回收号码队列从取序号时，需判断此序号是否已过期。
			if (now - seqNo.getDate().getTime() > period * 60000) {
				// 过期则跳过，留待应用实例启动时重新装载回收队列
				continue;
			}
			break;
		}
		// 查询不到则从序列服务查询新号段，并写入号码表
		if (seqNo == null) {
			return super.next();
		}
		// 删除被取走的号
		seqNoBiz.delete(catalog, code, seqNo.getNumber());
		return seqNo.getNumber();
	}

	/**
	 * 先写入回收号码表，再入队
	 * 
	 * @param no
	 */
	public void back(long no) {
		seqNoBiz.save(catalog, code, no, this.tag()); // 失败则抛出异常
		queue.add(new SeqNumber(no)); // 入队不成功也没关系
	}

	public int getPeriod() {
		return period;
	}

	public void setPeriod(int period) {
		this.period = period;
	}

	public Queue<SeqNumber> getQueue() {
		return queue;
	}

	public void setQueue(Queue<SeqNumber> queue) {
		this.queue = queue;
	}
}